﻿#include "CMutex.h"


#if defined( APP_PLATFORM_WINDOWS )
namespace app {

CMutex::CMutex(EMutexType type/* = EMT_RECURSIVE*/) {
    ::InitializeCriticalSectionAndSpinCount(&mCriticalSection, 4000);
}

CMutex::~CMutex() {
    ::DeleteCriticalSection(&mCriticalSection);
}

bool CMutex::tryLock(long milliseconds) {
    const s32 sleepMillis = 5;
    u32 iTime = 0;
    do {
        if(::TryEnterCriticalSection(&mCriticalSection) == TRUE) {
            return true;
        }
        ::Sleep(sleepMillis);
        iTime += sleepMillis;
    } while(iTime <= milliseconds);

    return false;
}

void CMutex::lock() {
    ::EnterCriticalSection(&mCriticalSection);
}

bool CMutex::tryLock() {
    return TRUE == ::TryEnterCriticalSection(&mCriticalSection);
}

void CMutex::unlock() {
    ::LeaveCriticalSection(&mCriticalSection);
}

void* CMutex::getHandle() {
    return &mCriticalSection;
}

} //namespace app

#elif defined( APP_PLATFORM_ANDROID )  || defined( APP_PLATFORM_LINUX )

#if defined(APP_HAVE_MUTEX_TIMEOUT)
#include <sys/time.h>
#endif //APP_HAVE_MUTEX_TIMEOUT

namespace app {

CMutex::CMutex(EMutexType type/* = EMT_RECURSIVE*/) {
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_PRIVATE);
    switch(type) {
    default:
    case EMT_RECURSIVE:
        //pthread_mutexattr_settype_np(&attr, PTHREAD_MUTEX_RECURSIVE_NP);
        pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
        break;
    case EMT_NO_RECURSIVE:
        //pthread_mutexattr_settype_np(&attr, PTHREAD_MUTEX_NORMAL_NP);
        pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);
        break;
    }
    if(pthread_mutex_init(&mMutex, &attr)) {
        //("cannot create mutex");
    }
    pthread_mutexattr_destroy(&attr);
}


CMutex::~CMutex() {
    pthread_mutex_destroy(&mMutex);
}

void* CMutex::getHandle() {
    return &mMutex;
}


bool CMutex::tryLock(long milliseconds) {
#if defined(APP_HAVE_MUTEX_TIMEOUT)
    struct timespec abstime;
    struct timeval tv;
    gettimeofday(&tv, NULL);
    abstime.tv_sec = tv.tv_sec + milliseconds / 1000;
    abstime.tv_nsec = tv.tv_usec * 1000 + (milliseconds % 1000) * 1000000;
    if(abstime.tv_nsec >= 1000000000) {
        abstime.tv_nsec -= 1000000000;
        abstime.tv_sec++;
    }
    s32 rc = pthread_mutex_timedlock(&mMutex, &abstime);
    if(rc == 0) {
        return true;
    } else if(rc == ETIMEDOUT) {
        return false;
    } else {
        //("cannot lock mutex");
        return false;
    }
#else
    const s32 sleepMillis = 5;
    u32 iTime = 0;
    do {
        s32 rc = pthread_mutex_trylock(&mMutex);
        if(rc == 0) {
            return true;
        } else if(rc != EBUSY) {
            //throw ("cannot lock mutex");
            break;
        }

        struct timespec ts;
        ts.tv_sec = 0;
        ts.tv_nsec = sleepMillis * 1000000;
        nanosleep(&ts, NULL);
        iTime += sleepMillis;
        /*
        struct timeval tv;
        tv.tv_sec  = 0;
        tv.tv_usec = sleepMillis * 1000;
        select(0, NULL, NULL, NULL, &tv);
        */
    } while(iTime <= milliseconds);

    return false;
#endif
}


void CMutex::lock() {
    if(pthread_mutex_lock(&mMutex)) {
        //("cannot lock mutex");
    }
}


bool CMutex::tryLock() {
    switch(pthread_mutex_trylock(&mMutex)) {
    case 0:
        return true;
    case EBUSY: //The mutex could not be acquired because it was already locked.
        return false;
    case EINVAL: //mutex have not been initialized.
    case EAGAIN: //exceed max lock count.
    default:
        return false;
    }
}


void CMutex::unlock() {
    if(pthread_mutex_unlock(&mMutex)) {
        //("cannot unlock mutex");
    }
}


} //namespace app
#endif
